iT邦幫忙

2025 iThome 鐵人賽

DAY 6
0

今天主要會講的部分有

  • Common Concepts
  • Ownership

相信各位已經有自己熟知的程式語言,所以這裡不會鉅細彌遺的講過所有語法,如果是第一次學習程式語言,可以去官網上找更完整的介紹.

接下來的示範都會在昨天建立的專案底下實作,如果還沒創建好專案,可以先去看看昨天的文章.

創建完後的結構應該會長成這樣
https://ithelp.ithome.com.tw/upload/images/20250810/20177999haIqATyQDe.png

編輯完後在終端輸入指令

cargo run

就會幫你編譯並且執行

需要安裝套件的話 就在終端輸入

cargo add

相關套件可以去 https://crates.io/ 查看


Variables(變數宣告)

在 Rust 中,變數預設是不可變(immutable),必須顯式聲明為 mut 才能改值。

另外有 const 與 shadowing 的概念。

(1) let

建立不可變變數。

fn main() {
    let x = 5; // 不可變
    println!("x = {}", x);
    // x = 6; // ❌ 編譯錯誤:不可變
}

(2) mut

建立可變變數。

fn main() {
    let mut y = 10; // 可變
    println!("y = {}", y);
    y = 20; // ✅ 可以改值
    println!("y = {}", y);
}

(3) const

常數必須在編譯時就決定,且型別必須標明。

const PI: f64 = 3.14159;
fn main() {
    println!("PI = {}", PI);
}

(4) Shadowing(遮蔽)

同名變數重新宣告,不需要 mut,可以改型別。

fn main() {
    let z = 5;
    let z = z + 1; // 新的 z
    let z = "Hello"; // 型別改成字串
    println!("z = {}", z);
}

Datatype(資料型別)

Rust 是靜態型別語言,但可推斷型別。

(1) 整數 integer

fn main() {
    let a: i32 = -42; // 32位整數
    let b: u64 = 100; // 無號64位整數
    println!("a = {}, b = {}", a, b);
}

(2) 浮點數 float

fn main() {
    let pi: f64 = 3.14; // 預設 f64
    let e: f32 = 2.71;  // 單精度
    println!("pi = {}, e = {}", pi, e);
}

(3) 布林 bool

fn main() {
    let is_rust_fun: bool = true;
    println!("Rust 好玩嗎?{}", is_rust_fun);
}

(4) 字元 char

fn main() {
    let letter: char = 'A';
    let emoji: char = '🦀';
    println!("字母: {}, Emoji: {}", letter, emoji);
}

Fn(函式)

Rust 函式使用 fn 宣告,必須明確參數型別,回傳值型別用 -> 表示。

fn main() {
    greet("Alice");
    let sum = add(3, 5);
    println!("3 + 5 = {}", sum);
}

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

fn add(x: i32, y: i32) -> i32 {
    x + y // 省略分號表示回傳
}

Control Flow(流程控制)

(1) if / else

fn main() {
    let number = 10;
    if number > 0 {
        println!("正數");
    } else if number < 0 {
        println!("負數");
    } else {
        println!("零");
    }
}

(2) loop(無限迴圈)

fn main() {
    let mut count = 0;
    loop {
        count += 1;
        if count == 3 {
            break;
        }
    }
    println!("計數結束: {}", count);
}

(3) while

fn main() {
    let mut n = 5;
    while n > 0 {
        println!("n = {}", n);
        n -= 1;
    }
}

(4) for

fn main() {
    for i in 1..=3 {
        println!("i = {}", i);
    }
}

以上是關於資料型態跟變數的部分,接著我們要進入相當重要的 - Ownership 的概念

在開始介紹前,官網上提到了 Stack 跟 Heap 兩種記憶體儲存方式的差別,這邊做一個總結

Stack 的操作非常簡單,新增資料只要把資料堆在最後(或最上面)一格就好,但如果要放資料到 Heap 的話,要考慮容量夠不夠大,不夠的話要再去要更多的空間,所以在效能上 Stack 會比 Heap 好.

此外,Stack 上放的資料必須是已知固定大小,像是整數、浮點數,布林值或 Array 之類的資料,如果像是 Vector 、String 這種可能變更大小的資料就會被放在 Heap.

這一個觀念在學習 Rust 的時候非常重要.

Ownership

所謂的 Ownership 做的就是記憶體管理,如果過去學過 C 語言的可能不陌生,但如果是學習 python 為主的話,這部分可能需要一點時間習慣.

那開始之前,先說明一下為什麼需要 Ownership,最主要的原因就是為了 Concurrency,在多個執行緒下要保證不出狀況,在撰寫程式碼的時候就需要加入規則去管理這些記憶體的配置和釋放.

底下我們透過程式碼來介紹 Ownership ,現在有一個 function,用來獲取球員上場得分

fn main() {
    let points = get_points();
    println!("{:?}", points);
}

fn get_points() -> Vec<i32> {
    let points = vec![23, 11, 10];
    return points;
}

當在 main 裡呼叫 get_points 的時候,points 這個變數的所有權是屬於 main 這個 scope 的,所以即使 get_points 這個 scope 結束,他依然不會釋放.

但假設像底下的情況

fn main() {
    let points = get_points();
    let total_point = get_total_point(points);
    println!("{:?}", points);
}

fn get_points() -> Vec<i32> {
    let points = vec![23, 11, 10];
    return points;
}

fn get_total_point(points: Vec<i32>) -> i32 {
	let mut total = 0;
		
    for point in points.iter() {
        total += point;
    }
		
	return total;
}

這裡就會報錯 因為在 let total_point = get_total_point(points); 的時候 已經將所有權轉移給 get_total_point了,所以當get_total_point這個scope結束後,points就被釋放掉了

所以為了解決這個問題,我們必須這麼修改

fn main() {
    let points = get_points();
    let total_point = get_total_point(&mut points);
    println!("{:?}", points);
}

fn get_points() -> Vec<i32> {
    let points = vec![23, 11, 10];
    return points;
}

fn get_total_point(points: &mut Vec<i32>) -> i32 {
	let mut total = 0;
		
    for point in points.iter() {
        total += point;
    }
		
	return total;
}

在這裡,加入了 &mut ,這樣一來即使points的所有權就還會是在main這個scope裡.

希望上面的介紹能讓各位認識 Ownership,底下我出了一個範例,可以想一下過程中buffer是屬於哪個scope呢,可以先想完後,再對答案.

但開始前,因為底下程式碼有用到 move 的功能,這邊解釋一下

作用

move 會強制閉包(closure)取得外層變數的所有權(ownership),而不是借用(reference)它。

這樣閉包內的變數會被移動(moved)到閉包裡,外層變數就不能再使用。


為什麼需要 move

  • 把資料帶進多執行緒(thread)中,確保生命週期安全。
  • 避免閉包外層變數被修改或釋放後,閉包內還在使用。
  • 當你需要閉包「擁有」資料(而不是只是借用)時。

這邊一樣舉個例子

借用版(沒有move)

fn main() {
    let data = vec![1, 2, 3];

    let closure = || {
        println!("{:?}", data); // 借用
    };

    closure();
    println!("{:?}", data); // ✅ 還能用
}

move版(擁有權移動)

fn main() {
    let data = vec![1, 2, 3];

    let closure = move || {
        println!("{:?}", data); // 擁有權被移進來
    };

    closure();
    // println!("{:?}", data); // ❌ 編譯錯誤,data 已被移走
}

這樣的功能在處理多個執行緒的時候,可以確保同一時間只有一個執行序能修改資料,非常重要.

動腦時間

fn log_len(s: &str) {
    println!("log_len: {}", s.len());
}

fn take_and_give_back(s: String) -> String {
    println!("take_and_give_back got: {}", s);
    s
}

fn main() {
    let mut title = String::from("Rust");

    log_len(&title); 
    {
        let mut buffer: Vec<String> = Vec::new(); 

        buffer.push(title);

        let first_ref: &str = &buffer[0];
        log_len(first_ref); 

        let consume_and_return = move || {
            let mut b = buffer; 
            let x = b.pop().expect("buffer had one element");
            let x = take_and_give_back(x); 
            b.push(x);                     
            b 
        };

        let mut buffer = consume_and_return();
        buffer.push(String::from("Hello from scope A"));

        println!("buffer in scope A has {} items", buffer.len());
    }
}

宣告點(內層 scope A 開始)

let mut buffer: Vec<String> = Vec::new();
  • 這一刻,buffer 的所有權屬於「內層 scope A」(也就是 { ... } 這段花括號包住的區塊)。
  • buffer 活到離開這個 scope 為止。

closure 使用 move 捕獲

let consume_and_return = move || { /* 使用 buffer */ };
  • 由於 move,buffer 的所有權從「內層 scope A」移進閉包的環境(environment)。
  • 也就是說,在閉包被建立的那一行之後,外面的 buffer 不再可用(已被捕獲移動)。

呼叫 closure 並接收回傳

let mut buffer = consume_and_return();
  • 關鍵:閉包內回傳了 b(原本的 buffer),所以呼叫完之後,重新取得 buffer 的所有權,但這次是透過shadowing(再次 let 同名變數)在「內層 scope A」中拿回。
  • 此時,buffer 又屬於「內層 scope A」。

離開內層 scope A

  • 花括號 } 結束時,buffer 的所有權隨 scope 結束而被 drop,釋放其中的 String 資源。
  • 這也解釋了為什麼在外層(main 之後的那幾行)不能再使用 buffer。

今天就先講到這,因為不知道應該要切在哪一個部分,所以就一次講完,東西有點多哈哈.


上一篇
【Day5】- 如何結合 Python 和 Rust
下一篇
【Day7】- Rust(Slice, Struct)
系列文
NautilusTrader 架構解析:Rust 在高效能量化交易平台中的角色與優勢22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言